package main

import (
	"fmt"
	"time"
)

/*
for 语句的常见“坑”与避坑方法
*/

func main() {
	//keng1()
	//keng2()
	//keng31()
	keng32()
}

func keng1() {
	/*问题一：对一个整型切片进行遍历，并且在每次循环体的迭代中都会创建一个新的Goroutine，输出这次迭代的元素的下标值与元素值*/
	var m = []int{1, 2, 3, 4, 5}
	for i, v := range m {
		go func() {
			fmt.Print(i, "=>", v, ",")
		}()
	}
	time.Sleep(time.Second * 1)
	fmt.Println()
	//预期结果: 0=>1,1=>2,2=>3,3=>4,4=>5, (以为每次迭代都会重新声明两个新的变量 i 和 v)
	//实际结果: 4=>5,4=>5,4=>5,4=>5,4=>5, (这些循环变量在 for range 语句中仅会被声明一次，且在每次迭代中都会被重用)

	//Goroutine 执行的闭包函数引用了它的外层包裹函数中的变量 i、v，这样变量 i、v 在主Goroutine 和新启动的 Goroutine 之间实现了共享， 而 i, v 值在整个循环过程中是重用的，仅有一份。
	//在 for range 循环结束后，i = 4, v = 5，因此各个 Goroutine 在等待 3 秒 后进行输出的时候，输出的是 i, v 的最终值。

	//--- 解决办法:可以为闭包函数增加参数，并且在创建 Goroutine 时将参数与 i、v 的当时值进行绑定
	for i, v := range m {
		go func(i, v int) {
			fmt.Print(i, "=>", v, ",")
		}(i, v)
	}
	time.Sleep(time.Second * 1)
	fmt.Println()
	//执行结果:4=>5,1=>2,3=>4,2=>3,0=>1, (备注:每次执行的顺序可能不一致,这是由 Goroutine 的调度所决定的)
}

func keng2() {
	/* 问题二：在 for...range 循环中参与循环的是range的副本 */
	var a = [5]int{1, 2, 3, 4, 5}
	var r [5]int
	fmt.Println("原始的a=", a) // [1 2 3 4 5]
	for i, v := range a {
		if i == 0 {
			a[1] = 12
			a[2] = 13
		}
		r[i] = v
	}
	fmt.Println("循环后a =", a) // [1 12 13 4 5]
	fmt.Println("循环后r =", r) // [1 2 3 4 5]
	fmt.Println()

	//修改一种方式,用切片 a[:] 替代原先的数组a
	fmt.Println("原始的a=", a) // [1 2 3 4 5]
	for i, v := range a[:] {
		if i == 0 {
			a[1] = 12
			a[2] = 13
		}
		r[i] = v
	}
	fmt.Println("循环后a =", a) // [1 12 13 4 5]
	fmt.Println("循环后r =", r) // [1 12 13 4 5]
	fmt.Println()
}

func keng31() {
	/*问题三：遍历 map 中元素的随机性:如果在循环的过程中，对map进行了修改，那么这样修改的结果是否会影响后续迭代？*/
	var map1 = map[string]int{
		"A": 21,
		"B": 22,
		"C": 23,
	}
	point := 0
	for k, v := range map1 {
		if point == 0 { //当 point 值为 0 时删除map中的一个元素
			delete(map1, "A")
		}
		point++
		fmt.Print(k, "=>", v, ",")
	}
	fmt.Println("point is ", point)

	//反复运行这个例子多次，可能会得到两个不同的结果:
	//当 k="A" 是第0个元素出现时会得到如下结果：A=>21,B=>22,C=>23,point is  3
	//当 k="A" 不是第0个元素出现时会得到如下结果：B=>22,C=>23,point is  2
}

func keng32() {
	//如果针对 map 类型的循环体中新创建了元素，那这项元素可能出现在后续循环中也可能不出现
	var map1 = map[string]int{
		"A": 21,
		"B": 22,
		"C": 23,
	}
	point := 0
	for k, v := range map1 {
		if point == 0 { //当 point 值为 0 时 给 map中新增一个元素
			map1["D"] = 24
		}
		point++
		fmt.Print(k, "=>", v, ",")
	}
	fmt.Println("point is ", point)
	//反复运行这个例子,执行结果也会有两种情况
	//A=>21,B=>22,C=>23,D=>24,point is  4
	//或者 A=>21,B=>22,C=>23,point is  3
}
